/*
 * jdbc-trace-wrapper
 * Copyright (C) 2010. The jdbc-trace-wrapper Project
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package nz.jdbcwrapper;

import java.lang.reflect.Constructor;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Savepoint;
import java.sql.Statement;
import java.util.Map;

public class WrapperConnection extends WarningHolder implements Connection
{
  static final String INCREMENTAL_KEY = "incremental";
  static final String REPORT_KEY = "report";

  private static final Class sybCallableStatement;
  private static final Constructor wrapperSybCallableStatement;
  private static final Class oraclePreparedStatement;
  private static final Constructor wrapperOraclePreparedStatement;

  static {
    Class callable;
    Constructor constructor;
    try
    {
      callable = Class.forName("com.sybase.jdbcx.SybCallableStatement");
      Class wrapperClass = Class.forName(WrapperConnection.class.getPackage().getName() + ".WrapperSybCallableStatement");
      constructor = wrapperClass.getConstructor(new Class[] {Data.class, Statistic.class, callable});
    }
    catch (Throwable e)
    {
      callable = null;
      constructor = null;
    }
    sybCallableStatement = callable;
    wrapperSybCallableStatement = constructor;

      try
      {
        callable = Class.forName("oracle.jdbc.OraclePreparedStatement");
        Class wrapperClass = Class.forName(WrapperConnection.class.getPackage().getName() + ".WrapperOraclePreparedStatement");
        constructor = wrapperClass.getConstructor(new Class[] {Data.class, Statistic.class, callable});
      }
      catch (Throwable e)
      {
        callable = null;
        constructor = null;
      }
      oraclePreparedStatement = callable;
      wrapperOraclePreparedStatement = constructor;
  }

  final Connection connection;
  final Data data;
  static Reporter reporter = null;
  boolean closed = false;

  WrapperConnection(Connection connection, Data data)
  {
    super("");
    this.connection = connection;
    this.data = data;

    if (data.trace >= 1)
      Util.trace(prefix + " Created connection");

    synchronized (Reporter.class)
    {
      if (reporter == null)
      {
        reporter = new Reporter();
        reporter.setDaemon(true);
        reporter.start();
      }
    }
    reporter.check(data);
  }

  public Statement createStatement() throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " createStatement()");

    try
    {
      WrapperStatement stmt = new WrapperStatement(
        data, null, connection.createStatement());
      if (data.trace >= 2)
        Util.traceEnd(key, stmt.prefix);
      return stmt;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }
  public PreparedStatement prepareStatement(String sql) throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " prepareStatement(" + Util.toString(sql, 8000, 0) + ")");

    try
    {
        PreparedStatement statement = connection.prepareStatement(sql);
        WrapperPreparedStatement stmt;

        if (wrapperOraclePreparedStatement != null && oraclePreparedStatement.isInstance(statement))
        {
          try
          {
            stmt = (WrapperPreparedStatement) wrapperOraclePreparedStatement.newInstance(new Object[] {data, Util.register(sql), statement});
          }
          catch (Exception e)
          {
            throw new WrapperException(e.toString());
          }
        }
        else {
            stmt = new WrapperPreparedStatement(
            data, Util.register(sql), statement);
        } // end of else

      if (data.trace >= 2)
        Util.traceEnd(key, stmt.prefix);
      return stmt;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public CallableStatement prepareCall(String sql) throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " prepareCall(" + Util.toString(sql, 8000, 0) + ")");

    try
    {
      CallableStatement call = connection.prepareCall(sql);
      WrapperCallableStatement stmt;

      if (wrapperSybCallableStatement != null && sybCallableStatement.isInstance(call))
      {
        try
        {
          stmt = (WrapperCallableStatement) wrapperSybCallableStatement.newInstance(new Object[] {data, Util.register(sql), call});
        }
        catch (Exception e)
        {
          throw new WrapperException(e.toString());
        }
      }
      else {
        stmt =  new WrapperCallableStatement(
        data, Util.register(sql), call);
      } // end of else

      if (data.trace >= 2)
        Util.traceEnd(key, stmt.prefix);
      return stmt;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }
  public String nativeSQL(String sql) throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " nativeSQL(" + Util.toString(sql, 8000, 0) + ")");

    try
    {
      String result = connection.nativeSQL(sql);
      if (data.trace >= 2)
        Util.traceEnd(key, result);
      return result;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public void setAutoCommit(boolean autoCommit) throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " setAutoCommit(" + autoCommit  + ")");

    try
    {
      connection.setAutoCommit(autoCommit);
      if (data.trace >= 2)
        Util.traceEnd(key, (String)null);
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public boolean getAutoCommit() throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " getAutoCommit()");

    try
    {
      boolean autoCommit = connection.getAutoCommit();
      if (data.trace >= 2)
        Util.traceEnd(key, String.valueOf(autoCommit));
      return autoCommit;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public void commit() throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " commit()");

    try
    {
      connection.commit();
      if (data.trace >= 2)
        Util.traceEnd(key, (String)null);
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public void rollback() throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " rollback()");

    try
    {
      connection.rollback();
      if (data.trace >= 2)
        Util.traceEnd(key, (String)null);
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public void close() throws SQLException
  {
    SQLException excp;
    long key = 0L;

    if (data.trace >= 1)
      key = Util.traceBegin(prefix + " close()");

    try
    {
      closed = true;
      connection.close();
      if (data.trace >= 1)
        Util.traceEnd(key, (String)null);
      return;
    }
    catch (SQLException e)
    {
      if (data.trace >= 1)
        Util.traceEnd(key, e);
      excp = e;
    }
    finally
    {
      if (reporter.onClose())
        Util.report(reporter.incremental);
      checkWarnings();
    }

    throw excp;
  }

  public boolean isClosed() throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " isClosed()");

    try
    {
      boolean closed = connection.isClosed();
      if (data.trace >= 2)
        Util.traceEnd(key, String.valueOf(closed));
      return closed;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }
  public DatabaseMetaData getMetaData() throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " getMetaData()");

    try
    {
      WrapperDatabaseMetaData result = new WrapperDatabaseMetaData(
        data, Util.register("getMetaData()"), connection.getMetaData());

      if (data.trace >= 2)
        Util.traceEnd(key, result.prefix);

      return result;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);

      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public void setReadOnly(boolean readOnly) throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " setReadOnly(" + readOnly + ")");

    try
    {
      connection.setReadOnly(readOnly);

      if (data.trace >= 2)
        Util.traceEnd(key, (String)null);
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public boolean isReadOnly() throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " isReadOnly()");

    try
    {
      boolean readOnly = connection.isReadOnly();

      if (data.trace >= 2)
        Util.traceEnd(key, String.valueOf(readOnly));
      return readOnly;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public void setCatalog(String catalog) throws SQLException
  {
    long key = 0;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " setCatalog(" + Util.toString(catalog, 8000, 0) + ")");

    try
    {
      connection.setCatalog(catalog);

      if (data.trace >= 2)
        Util.traceEnd(key, (String)null);
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public String getCatalog() throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " getCatalog()");

    try
    {
      String catalog = connection.getCatalog();

      if (data.trace >= 2)
        Util.traceEnd(key, catalog);
      return catalog;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public void setTransactionIsolation(int level) throws SQLException
  {
    long key = 0;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " setTransactionIsolation(" + level + ")");

    try
    {
      connection.setTransactionIsolation(level);

      if (data.trace >= 2)
        Util.traceEnd(key, (String)null);
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public int getTransactionIsolation() throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " getTransactionisolation()");

    try
    {
      int level = connection.getTransactionIsolation();

      if (data.trace >= 2)
        Util.traceEnd(key, String.valueOf(level));
      return level;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public SQLWarning getWarnings() throws SQLException
  {
    long key = 0;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " getWarnings()");

    try
    {
      SQLWarning warning = connection.getWarnings();

      if (data.trace >= 2)
        Util.traceEnd(key, String.valueOf(warning));
      return warning;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public void clearWarnings() throws SQLException
  {
    long key = 0;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " clearWarnings()");

    try
    {
      connection.clearWarnings();

      if (data.trace >= 2)
        Util.traceEnd(key, (String)null);
      warnings.clear();
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public Statement createStatement(int resultSetType,
                                   int resultSetConcurrency)
    throws SQLException
  {
    long key = 0L;
    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " createStatement(" +
                            resultSetType + ", " +
                            resultSetConcurrency + ")");

    try
    {
      WrapperStatement stmt = new WrapperStatement(
        data, null,
        connection.createStatement(resultSetType, resultSetConcurrency));

      if (data.trace >= 2)
        Util.traceEnd(key, stmt.prefix);
      return stmt;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public PreparedStatement prepareStatement(String sql,
                                            int resultSetType,
                                            int resultSetConcurrency)
    throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " prepareStatement(" +
                            Util.toString(sql, 8000, 0) + ", " +
                            resultSetType + ", " +
                            resultSetConcurrency + ")");

    try
    {
        PreparedStatement statement = connection.prepareStatement(
                sql, resultSetType, resultSetConcurrency);

        WrapperPreparedStatement stmt;

        if (wrapperOraclePreparedStatement != null && oraclePreparedStatement.isInstance(statement))
        {
          try
          {
            stmt = (WrapperPreparedStatement) wrapperOraclePreparedStatement.newInstance(new Object[] {data, Util.register(sql), statement});
          }
          catch (Exception e)
          {
            throw new WrapperException(e.toString());
          }
        }
        else {
            stmt = new WrapperPreparedStatement(
            data, Util.register(sql), statement);
        } // end of else

      if (data.trace >= 2)
        Util.traceEnd(key, stmt.prefix);
      return stmt;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public CallableStatement prepareCall(
    String sql,
    int resultSetType,
    int resultSetConcurrency) throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " prepareCall(" +
                            Util.toString(sql, 8000, 0) + ", " +
                            resultSetType + ", " +
                            resultSetConcurrency + ")");

    try
    {
        CallableStatement call = connection.prepareCall(sql, resultSetType, resultSetConcurrency);
        WrapperCallableStatement stmt;

        if (wrapperSybCallableStatement != null && sybCallableStatement.isInstance(call))
        {
          try
          {
            stmt = (WrapperCallableStatement) wrapperSybCallableStatement.newInstance(new Object[] {data, Util.register(sql), call});
          }
          catch (Exception e)
          {
            throw new WrapperException(e.toString());
          }
        }
        else {
          stmt =  new WrapperCallableStatement(
          data, Util.register(sql), call);
        } // end of else

      if (data.trace >= 2)
        Util.traceEnd(key, stmt.prefix);
      return stmt;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public Map getTypeMap() throws SQLException
  {
    long key = 0;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " getTypeMap()");

    try
    {
      Map map = connection.getTypeMap();

      if (data.trace >= 2)
        Util.traceEnd(key, (String)null);
      return map;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public void setTypeMap(Map map) throws SQLException
  {
    long key = 0;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " setTypeMap(" + Util.toString(map) + ")");

    try
    {
      connection.setTypeMap(map);

      if (data.trace >= 2)
        Util.traceEnd(key, (String)null);
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  /**
   * @see java.sql.Connection#createStatement(int, int, int)
   */
  public Statement createStatement(
    int resultSetType,
    int resultSetConcurrency,
    int resultSetHoldability)
    throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " createStatement(" + resultSetType + ", " + resultSetConcurrency + ", " + resultSetHoldability + ")");

    try
    {
      Statement result = connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);

      if (data.trace >= 2)
        Util.traceEnd(key, Util.toString(result));
      return result;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  /**
   * @see java.sql.Connection#getHoldability()
   */
  public int getHoldability() throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " getHoldability()");

    try
    {
      int result = connection.getHoldability();

      if (data.trace >= 2)
        Util.traceEnd(key, String.valueOf(result));
      return result;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  /**
   * @see java.sql.Connection#prepareCall(String, int, int, int)
   */
  public CallableStatement prepareCall(
    String sql,
    int resultSetType,
    int resultSetConcurrency,
    int resultSetHoldability)
    throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " prepareCall(" + Util.toString(sql, 8000, 0) + ", " + resultSetType + ", " + resultSetConcurrency + ", " + resultSetHoldability + ")");

    try
    {
      CallableStatement call = connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
        WrapperCallableStatement stmt;

        if (wrapperSybCallableStatement != null && sybCallableStatement.isInstance(call))
        {
          try
          {
            stmt = (WrapperCallableStatement) wrapperSybCallableStatement.newInstance(new Object[] {data, Util.register(sql), call});
          }
          catch (Exception e)
          {
            throw new WrapperException(e.toString());
          }
        }
        else {
          stmt =  new WrapperCallableStatement(
          data, Util.register(sql), call);
        } // end of else

      if (data.trace >= 2)
        Util.traceEnd(key, stmt.prefix);
      return stmt;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  /**
   * @see java.sql.Connection#prepareStatement(String, int, int, int)
   */
  public PreparedStatement prepareStatement(
    String sql,
    int resultSetType,
    int resultSetConcurrency,
    int resultSetHoldability)
    throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " prepareStatement(" + Util.toString(sql, 8000, 0) + ", " + resultSetType + ", " + resultSetConcurrency + ", " + resultSetHoldability + ")");

    try
    {
      PreparedStatement statement = connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);

        WrapperPreparedStatement stmt;

        if (wrapperOraclePreparedStatement != null && oraclePreparedStatement.isInstance(statement))
        {
          try
          {
            stmt = (WrapperPreparedStatement) wrapperOraclePreparedStatement.newInstance(new Object[] {data, Util.register(sql), statement});
          }
          catch (Exception e)
          {
            throw new WrapperException(e.toString());
          }
        }
        else {
            stmt = new WrapperPreparedStatement(
            data, Util.register(sql), statement);
        } // end of else

      if (data.trace >= 2)
        Util.traceEnd(key, stmt.prefix);
      return stmt;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  /**
   * @see java.sql.Connection#prepareStatement(String, int)
   */
  public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
    throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " prepareStatement(" + Util.toString(sql, 8000, 0) + ", " + autoGeneratedKeys + ")");

    try
    {
      PreparedStatement statement = connection.prepareStatement(sql, autoGeneratedKeys);
        WrapperPreparedStatement stmt;

        if (wrapperOraclePreparedStatement != null && oraclePreparedStatement.isInstance(statement))
        {
          try
          {
            stmt = (WrapperPreparedStatement) wrapperOraclePreparedStatement.newInstance(new Object[] {data, Util.register(sql), statement});
          }
          catch (Exception e)
          {
            throw new WrapperException(e.toString());
          }
        }
        else {
            stmt = new WrapperPreparedStatement(
            data, Util.register(sql), statement);
        } // end of else


      if (data.trace >= 2)
        Util.traceEnd(key, stmt.prefix);
      return stmt;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  /**
   * @see java.sql.Connection#prepareStatement(String, int[])
   */
  public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
    throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " prepareStatement(" + Util.toString(sql, 8000, 0) + ", " + Util.toString(columnIndexes) + ")");

    try
    {
      PreparedStatement statement = connection.prepareStatement(sql, columnIndexes);
        WrapperPreparedStatement stmt;

        if (wrapperOraclePreparedStatement != null && oraclePreparedStatement.isInstance(statement))
        {
          try
          {
            stmt = (WrapperPreparedStatement) wrapperOraclePreparedStatement.newInstance(new Object[] {data, Util.register(sql), statement});
          }
          catch (Exception e)
          {
            throw new WrapperException(e.toString());
          }
        }
        else {
            stmt = new WrapperPreparedStatement(
            data, Util.register(sql), statement);
        } // end of else

      if (data.trace >= 2)
        Util.traceEnd(key, stmt.prefix);
      return stmt;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  /**
   * @see java.sql.Connection#prepareStatement(String, String[])
   */
  public PreparedStatement prepareStatement(String sql, String[] columnNames)
    throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " prepareStatement(" + Util.toString(sql, 8000, 0) + ", " + Util.toString(columnNames) + ")");

    try
    {
      PreparedStatement statement = connection.prepareStatement(sql, columnNames);
        WrapperPreparedStatement stmt;

        if (wrapperOraclePreparedStatement != null && oraclePreparedStatement.isInstance(statement))
        {
          try
          {
            stmt = (WrapperPreparedStatement) wrapperOraclePreparedStatement.newInstance(new Object[] {data, Util.register(sql), statement});
          }
          catch (Exception e)
          {
            throw new WrapperException(e.toString());
          }
        }
        else {
            stmt = new WrapperPreparedStatement(
            data, Util.register(sql), statement);
        } // end of else

      if (data.trace >= 2)
        Util.traceEnd(key, stmt.prefix);
      return stmt;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  /**
   * @see java.sql.Connection#releaseSavepoint(Savepoint)
   */
  public void releaseSavepoint(Savepoint savepoint) throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " releaseSavepoint(" + Util.toString(savepoint) + ")");

    try
    {
      connection.releaseSavepoint(savepoint);

      if (data.trace >= 2)
        Util.traceEnd(key, (String)null);
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  /**
   * @see java.sql.Connection#rollback(Savepoint)
   */
  public void rollback(Savepoint savepoint) throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " rollback(" + Util.toString(savepoint) + ")");

    try
    {
      connection.rollback(savepoint);

      if (data.trace >= 2)
        Util.traceEnd(key, (String)null);
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  /**
   * @see java.sql.Connection#setHoldability(int)
   */
  public void setHoldability(int holdability) throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " setHoldability(" + holdability + ")");

    try
    {
      connection.setHoldability(holdability);

      if (data.trace >= 2)
        Util.traceEnd(key, (String)null);
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  /**
   * @see java.sql.Connection#setSavepoint()
   */
  public Savepoint setSavepoint() throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " setSavepoint()");

    try
    {
      Savepoint result = connection.setSavepoint();

      if (data.trace >= 2)
        Util.traceEnd(key, Util.toString(result));
      return result;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  /**
   * @see java.sql.Connection#setSavepoint(String)
   */
  public Savepoint setSavepoint(String name) throws SQLException
  {
    long key = 0L;

    if (data.trace >= 2)
      key = Util.traceBegin(prefix + " setSavepoint(" + Util.toString(name) + ")");

    try
    {
      Savepoint result = connection.setSavepoint(name);

      if (data.trace >= 2)
        Util.traceEnd(key, Util.toString(result));
      return result;
    }
    catch (SQLException e)
    {
      if (data.trace >= 2)
        Util.traceEnd(key, e);
      throw e;
    }
    finally
    {
      checkWarnings();
    }
  }

  public Connection getJdbcConnection() {
      return connection;
  }

  SQLWarning getProxyWarnings() throws SQLException
  {
    return closed ? null : connection.getWarnings();
  }

//  public boolean isWrapperFor(Class<?> iface) throws SQLException
//  {
//      return iface.isInstance(this) || connection.isWrapperFor(iface);
//  }
//
//  public <T> T unwrap(Class<T> iface) throws SQLException
//  {
//      return iface.isInstance(this) ? (T)this : connection.unwrap(iface);
//  }
//  
//  
//
//    public Array createArrayOf(String typeName, Object[] elements) throws SQLException
//    {
//        long key = 0L;
//        if (data.trace >= 4)
//            key = Util.traceBegin(prefix + " createArrayOf(typeName, elements)");
//
//        long duration = Long.MIN_VALUE;
//        long start = 0L;
//
//        try
//        {
//            start = System.currentTimeMillis();
//            Array result = connection.createArrayOf(typeName, elements);
//            duration = System.currentTimeMillis() - start;
//
//            if (data.trace >= 4)
//                Util.traceEnd(key, Util.toString(result));
//            return result;
//        }
//        catch (SQLException e)
//        {
//            if (data.trace >= 4)
//                Util.traceEnd(key, e);
//            throw e;
//        }
//    }
//
//    public Blob createBlob() throws SQLException
//    {
//        long key = 0L;
//        if (data.trace >= 4)
//            key = Util.traceBegin(prefix + " createBlob()");
//
//        long duration = Long.MIN_VALUE;
//        long start = 0L;
//
//        try
//        {
//            start = System.currentTimeMillis();
//            Blob result = connection.createBlob();
//            duration = System.currentTimeMillis() - start;
//
//            if (data.trace >= 4)
//                Util.traceEnd(key, Util.toString(result));
//            return result;
//        }
//        catch (SQLException e)
//        {
//            if (data.trace >= 4)
//                Util.traceEnd(key, e);
//            throw e;
//        }
//    }
//
//    public Clob createClob() throws SQLException
//    {
//        long key = 0L;
//        if (data.trace >= 4)
//            key = Util.traceBegin(prefix + " createClob()");
//
//        long duration = Long.MIN_VALUE;
//        long start = 0L;
//
//        try
//        {
//            start = System.currentTimeMillis();
//            Clob result = connection.createClob();
//            duration = System.currentTimeMillis() - start;
//
//            if (data.trace >= 4)
//                Util.traceEnd(key, Util.toString(result));
//            return result;
//        }
//        catch (SQLException e)
//        {
//            if (data.trace >= 4)
//                Util.traceEnd(key, e);
//            throw e;
//        }
//    }
//
//    public NClob createNClob() throws SQLException
//    {
//        long key = 0L;
//        if (data.trace >= 4)
//            key = Util.traceBegin(prefix + " createNClob()");
//
//        long duration = Long.MIN_VALUE;
//        long start = 0L;
//
//        try
//        {
//            start = System.currentTimeMillis();
//            NClob result = connection.createNClob();
//            duration = System.currentTimeMillis() - start;
//
//            if (data.trace >= 4)
//                Util.traceEnd(key, Util.toString(result));
//            return result;
//        }
//        catch (SQLException e)
//        {
//            if (data.trace >= 4)
//                Util.traceEnd(key, e);
//            throw e;
//        }
//    }
//
//    public Properties getClientInfo() throws SQLException
//    {
//        long key = 0L;
//        if (data.trace >= 4)
//            key = Util.traceBegin(prefix + " getClientInfo()");
//
//        long duration = Long.MIN_VALUE;
//        long start = 0L;
//
//        try
//        {
//            start = System.currentTimeMillis();
//            Properties result = connection.getClientInfo();
//            duration = System.currentTimeMillis() - start;
//
//            if (data.trace >= 4)
//                Util.traceEnd(key, Util.toString(result));
//            return result;
//        }
//        catch (SQLException e)
//        {
//            if (data.trace >= 4)
//                Util.traceEnd(key, e);
//            throw e;
//        }
//    }
//
//    public SQLXML createSQLXML() throws SQLException
//    {
//        long key = 0L;
//        if (data.trace >= 4)
//            key = Util.traceBegin(prefix + " createSQLXML()");
//
//        long duration = Long.MIN_VALUE;
//        long start = 0L;
//
//        try
//        {
//            start = System.currentTimeMillis();
//            SQLXML result = connection.createSQLXML();
//            duration = System.currentTimeMillis() - start;
//
//            if (data.trace >= 4)
//                Util.traceEnd(key, Util.toString(result));
//            return result;
//        }
//        catch (SQLException e)
//        {
//            if (data.trace >= 4)
//                Util.traceEnd(key, e);
//            throw e;
//        }
//    }
//
//    public String getClientInfo(String name) throws SQLException
//    {
//        long key = 0L;
//        if (data.trace >= 4)
//            key = Util.traceBegin(prefix + " getClientInfo(name)");
//
//        long duration = Long.MIN_VALUE;
//        long start = 0L;
//
//        try
//        {
//            start = System.currentTimeMillis();
//            String result = connection.getClientInfo(name);
//            duration = System.currentTimeMillis() - start;
//
//            if (data.trace >= 4)
//                Util.traceEnd(key, Util.toString(result));
//            return result;
//        }
//        catch (SQLException e)
//        {
//            if (data.trace >= 4)
//                Util.traceEnd(key, e);
//            throw e;
//        }
//    }
//
//    public Struct createStruct(String typeName, Object[] attributes) throws SQLException
//    {
//        long key = 0L;
//        if (data.trace >= 4)
//            key = Util.traceBegin(prefix + " createStruct(typeName, attributes)");
//
//        long duration = Long.MIN_VALUE;
//        long start = 0L;
//
//        try
//        {
//            start = System.currentTimeMillis();
//            Struct result = connection.createStruct(typeName, attributes);
//            duration = System.currentTimeMillis() - start;
//
//            if (data.trace >= 4)
//                Util.traceEnd(key, Util.toString(result));
//            return result;
//        }
//        catch (SQLException e)
//        {
//            if (data.trace >= 4)
//                Util.traceEnd(key, e);
//            throw e;
//        }
//    }
//
//    public boolean isValid(int timeout) throws SQLException
//    {
//        long key = 0L;
//        if (data.trace >= 4)
//            key = Util.traceBegin(prefix + " isValid(timeout)");
//
//        long duration = Long.MIN_VALUE;
//        long start = 0L;
//
//        try
//        {
//            start = System.currentTimeMillis();
//            boolean result = connection.isValid(timeout);
//            duration = System.currentTimeMillis() - start;
//
//            if (data.trace >= 4)
//                Util.traceEnd(key, Util.toString(result));
//            return result;
//        }
//        catch (SQLException e)
//        {
//            if (data.trace >= 4)
//                Util.traceEnd(key, e);
//            throw e;
//        }
//    }
//
//    public void setClientInfo(Properties properties) throws SQLClientInfoException
//    {
//        long key = 0L;
//        if (data.trace >= 4)
//            key = Util.traceBegin(prefix + " setClientInfo(properties)");
//
//        long duration = Long.MIN_VALUE;
//        long start = 0L;
//
//        try
//        {
//            start = System.currentTimeMillis();
//            connection.setClientInfo(properties);
//            duration = System.currentTimeMillis() - start;
//
//            if (data.trace >= 4)
//                Util.traceEnd(key, (String)null);
//            return;
//        }
//        catch (SQLClientInfoException e)
//        {
//            if (data.trace >= 4)
//                Util.traceEnd(key, e);
//            throw e;
//        }
//    }
//
//    public void setClientInfo(String name, String value) throws SQLClientInfoException
//    {
//        long key = 0L;
//        if (data.trace >= 4)
//            key = Util.traceBegin(prefix + " setClientInfo(name, value)");
//
//        long duration = Long.MIN_VALUE;
//        long start = 0L;
//
//        try
//        {
//            start = System.currentTimeMillis();
//            connection.setClientInfo(name, value);
//            duration = System.currentTimeMillis() - start;
//
//            if (data.trace >= 4)
//                Util.traceEnd(key, (String)null);
//            return;
//        }
//        catch (SQLClientInfoException e)
//        {
//            if (data.trace >= 4)
//                Util.traceEnd(key, e);
//            throw e;
//        }
//    }




    private static class Reporter extends Thread
    {
        boolean onClose = true;       // Whether to report at each close
        long period = 0L;             // If > 0, frequency of periodic reports.
        boolean incremental = false;  // Whether to clear statistics at each report

        public void run()
        {
          long lastReport = 0L;

          Thread finalReport = new Thread() {
                public void run(){
                  if (period > 0L)
                    Util.report(false);
                }
              };
          finalReport.setName("JdbcWrapper Report on Shutdown");
          finalReport.setDaemon(true);
          Runtime.getRuntime().addShutdownHook(finalReport);

          for (;;)
          {
            long sleepTime;
            boolean report;
            boolean incr;

            synchronized (this)
            {
              report = period > 0L;
              sleepTime = report
                ? lastReport + period - System.currentTimeMillis()
                : 999999;  // Will be woken when needed
              incr = incremental;
            }
            if (sleepTime > 0)
            {
              try
              {
                sleep(sleepTime);
              }
              catch (InterruptedException e)
              {
                // ignore
              }
              continue;
            }
            if (report)
              Util.report(incr);
            lastReport = System.currentTimeMillis();
          }
        }

        synchronized void check(Data data)
        {
          String incrementalOption = data.options.getProperty(INCREMENTAL_KEY);
          if (incrementalOption != null)
          {
            incrementalOption = incrementalOption.trim().toLowerCase();
            incremental = incrementalOption.equals("yes") ||
              incrementalOption.equals("true") ||
              incrementalOption.equals("on") ||
              incrementalOption.equals("1");
          }
          String reportOption = data.options.getProperty(REPORT_KEY);
          if (reportOption == null)
            Util.timingsMaxSize = 0;
          else
          {
            reportOption = reportOption.trim().toLowerCase();
            try
            {
              long value = Long.parseLong(reportOption);
              onClose = false;
              period = value > 0 ? value : 0L;
            }
            catch (NumberFormatException e)
            {
              period = 0L;
              onClose = reportOption.equals("close");
            }
          }
          interrupt();
        }

        synchronized boolean onClose()
        {
          return onClose;
        }

        synchronized boolean incremental()
        {
          return incremental;
        }
    }
}
